package com.wyd.flowable;

import com.wyd.flowable.custom.DelegateTaskCmdCustom;
import com.wyd.flowable.dto.CompleteTask;
import com.wyd.flowable.dto.ProcessInstanceCreateRequest;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.common.engine.api.FlowableIllegalArgumentException;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.engine.*;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ModelQuery;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.repository.ProcessDefinitionQuery;
import org.flowable.engine.runtime.ActivityInstance;
import org.flowable.engine.runtime.DataObject;
import org.flowable.engine.runtime.Execution;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.task.Attachment;
import org.flowable.identitylink.service.impl.persistence.entity.IdentityLinkEntity;
import org.flowable.image.ProcessDiagramGenerator;
import org.flowable.task.api.DelegationState;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.flowable.task.service.impl.persistence.entity.TaskEntityImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Api(tags = {"FlowableController"})
@RestController
public class FlowableController {
    @Autowired
    protected RepositoryService repositoryService;
    @Autowired
    protected RuntimeService runtimeService;
    @Resource
    protected ProcessEngine processEngine;
    @Autowired
    protected ProcessEngineConfiguration processEngineConfiguration;
    @Autowired
    protected TaskService taskService;

    @GetMapping("/test")
    public String test() {
        return "ok";
    }

    @Transactional(rollbackFor = Exception.class)
    @ApiOperation("部署")
    @PostMapping("/deployment")
    public Object deployment(@RequestPart MultipartFile file, String deploymentName,
                             String deploymentKey, String tenantId, String category) throws IOException {
        if (file.getOriginalFilename() == null || !file.getOriginalFilename().endsWith(".bpmn20.xml")) {
            return "文件名称必须.bpmn20.xml结尾";
        }

        final InputStream inputStream = file.getInputStream();
        final Deployment deployment = repositoryService.createDeployment()
                .addInputStream(file.getOriginalFilename(), inputStream)
                .key(deploymentKey).name(deploymentName).tenantId(tenantId).category(category)
                .deploy();
        IOUtils.closeQuietly(inputStream);

        log.info("部署成功：部署id={}", deployment.getId());

        return deployment.getId();
    }

    @ApiOperation("获取流程定义列表")
    @GetMapping("ProcessDefinitionQuery")
    public Object listPd(String processDefinitionCategory) {
        final ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery()
                .latestVersion();

        if (StringUtils.isNotBlank(processDefinitionCategory)) {
            processDefinitionQuery.processDefinitionCategory(processDefinitionCategory);
        }

//        processDefinitionQuery.dec

        final List<ProcessDefinition> list = processDefinitionQuery.list();

        return list.size();
    }

    @ApiOperation("下载流程xml")
    @GetMapping("/getModelBytesByProcessDefinitionKey")
    public void getModelBytes(String processDefinitionKey, HttpServletResponse response) throws IOException {
        final ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionKey(processDefinitionKey)
                .latestVersion().singleResult();

        final InputStream inputStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), processDefinition.getResourceName());

        response.setContentType("application/octet-stream");
        response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(processDefinition.getResourceName(), StandardCharsets.UTF_8.displayName()));

        final ServletOutputStream outputStream = response.getOutputStream();

        IOUtils.copy(inputStream, outputStream);
        IOUtils.closeQuietly(inputStream);
    }

    @ApiOperation("发起流程实例")
    @PostMapping("/createProcessInstance")
    public Object createProcessInstance(@RequestBody @Validated ProcessInstanceCreateRequest request) {
        //设置流程发起人
        Authentication.setAuthenticatedUserId(request.getUserId());

        final ProcessInstance instance = runtimeService.createProcessInstanceBuilder()
                .processDefinitionKey(request.getProcessDefinitionKey())
                .variables(request.getVariables())
                .start();

        //这个方法最终使用一个ThreadLocal类型的变量进行存储，也就是与当前的线程绑定，所以流程实例启动完毕之后，需要设置为null，防止多线程的时候出问题。
        Authentication.setAuthenticatedUserId(null);

        return instance.getId();
    }

    @ApiOperation("查询任务")
    @GetMapping("/task")
    public Object task(String userId, @RequestParam Set<String> roles, String processDefinitionKey) {
        final TaskQuery taskQuery = processEngine.getTaskService().createTaskQuery()
                .or()
                .taskAssignee(userId)
                .taskCandidateOrAssigned(userId)
                .taskCandidateGroupIn(roles)
                .taskCandidateUser(userId)
                .endOr()
                ;

        if (StringUtils.isNotBlank(processDefinitionKey)) {
            taskQuery.processDefinitionKey(processDefinitionKey);
        }

        final List<Task> list = taskQuery
                .list();

        final List<Task> list2 = processEngine.getTaskService().createTaskQuery()
                .taskCandidateOrAssigned(userId).list();

        list.addAll(list2);

        log.info("list={}", list);

        return "ok";
//        return processEngine.getTaskService().createTaskQuery().or()
//                .taskAssignee(userId)
//                .taskCandidateUser(userId)
//                .taskCandidateGroupIn(roles).endOr().list();
    }

    @Transactional(rollbackFor = Exception.class)
    @ApiOperation("完成任务")
    @PostMapping("/completeTask")
    public Object completeTask(@RequestBody @Validated CompleteTask completeTask) {
        Task task = processEngine.getTaskService().createTaskQuery()
                .taskId(completeTask.getTaskId())
                .singleResult();

        // 如果没有认领先认领
        if (StringUtils.isBlank(task.getAssignee())) {
            processEngine.getTaskService().claim(completeTask.getTaskId(), completeTask.getUserId());
        }

        // 任务获取不到全局流程变量，单独获取
        final Integer nrOfInstances = (Integer) runtimeService.getVariable(task.getExecutionId(), "nrOfInstances");
        String nrOfInstancesKey = "nrOfInstances";
        String nrOfNoPassKey = "nrOfNoPassInstances";
        final ArrayList<String> list = new ArrayList<>();
        list.add(nrOfInstancesKey);
        list.add(nrOfNoPassKey);
        final Map<String, Object> map = runtimeService.getVariables(task.getExecutionId(), list);

        if (map.get(nrOfInstancesKey) != null) {
            // 多实例任务完成
            log.info("多实例任务完成任务 新增不通过数量");

            Integer o = (Integer) map.get(nrOfNoPassKey);
            if (o == null) {
                o = 0;
            }
            if (Boolean.FALSE.equals(completeTask.getPass())) {
                // 不通过
                o = o + 1;
            }

            runtimeService.setVariable(task.getExecutionId(), nrOfNoPassKey, o);
        }

        if (DelegationState.PENDING.equals(task.getDelegationState()) &&
                completeTask.getUserId().equals(task.getAssignee())) {
            // 被委派任务的办理: 办理完成后，委派任务会自动回到委派人的任务中
            processEngine.getTaskService().resolveTask(completeTask.getTaskId(), completeTask.getVariables());
        } else {

            try {
                processEngine.getTaskService().complete(completeTask.getTaskId(), completeTask.getVariables());
            } catch (FlowableException e) {
                log.error("完成任务发生异常", e);

                // 中文转化
                final String message = e.getMessage();

                if (message.contains("Unknown property used in expression:")) {
                    return message.replace("Unknown property used in expression:", "表达式中使用了未知属性:");
                }

                if (message.contains("No outgoing sequence flow of the exclusive gateway")
                        && message.contains("could be selected for continuing the process")) {
                    return message.replace("No outgoing sequence flow of the exclusive gateway", "网关")
                            .replace("could be selected for continuing the process", "找不到满足继续流转的条件，请检查流程图配置");
                }

                if (message.contains("No outgoing sequence flow of element")
                        && message.contains("could be selected for continuing the process")) {
                    return message.replace("No outgoing sequence flow of element", "元素")
                            .replace("could be selected for continuing the process", "找不到满足继续流转的条件，请检查流程图配置");
                }

                return message;
            }

        }

        return "ok";
    }

    @ApiOperation("委托给多个人")
    @PostMapping("/delegateTask")
    public Object delegateTask(@RequestParam String taskId, String owner, @RequestParam Set<String> userIds) {
        Task task = processEngine.getTaskService().createTaskQuery()
                .taskId(taskId).singleResult();

        if (task.getOwner() != null) {
            log.info("任务有所有者 owner={}", task.getOwner());
        } else {
            if (StringUtils.isBlank(owner)) {
                throw new RuntimeException("任务没有所有者，需要传入");
            }

            processEngine.getTaskService().setOwner(taskId, owner);
        }

        // 执行
        processEngine.getManagementService().executeCommand(new DelegateTaskCmdCustom(taskId, userIds));

        return "ok";
    }

    @ApiOperation("任务移交转办给多个人")
    @PostMapping("/setAssignee")
    public Object setAssignee(@RequestParam String taskId, @RequestParam String oldUserId, @RequestParam Set<Object> userIds) {
        if (userIds.contains(oldUserId)) {
            throw new RuntimeException("不能选自己");
        }

        Task task = processEngine.getTaskService().createTaskQuery()
                .taskId(taskId)
                .includeIdentityLinks()
                .includeProcessVariables()
                .singleResult();

        // 判断是否是多实例任务
        final String executionId = task.getExecutionId();
        final Execution execution = processEngine.getRuntimeService().createExecutionQuery().executionId(executionId).singleResult();
        log.info("task.getProcessInstanceId().equals(execution.getParentId())={}", task.getProcessInstanceId().equals(execution.getParentId()));
//        final ProcessInstance processInstance = processEngine.getRuntimeService().createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult();
//        final List<ActivityInstance> list = processEngine.getRuntimeService().createActivityInstanceQuery().activityId(task.getTaskDefinitionKey()).list();
//        final Map<String, DataObject> dataObjects = processEngine.getTaskService().getDataObjects(taskId);
//        final List<Attachment> processInstanceAttachments = processEngine.getTaskService().getProcessInstanceAttachments(task.getProcessInstanceId());
//        final ModelQuery modelQuery = processEngine.getRepositoryService().createModelQuery().deploymentId(processInstance.getDeploymentId());
//        final BpmnModel bpmnModel = processEngine.getRepositoryService().getBpmnModel(task.getProcessDefinitionId());


        // 约定一个多实例任务变量
//        final Object userlist = task.getProcessVariables().get("userlist");
        final Map<String, UserTask> allUserTaskMap = getAllUserTaskMap(task.getProcessDefinitionId());
        UserTask userTaskModel = allUserTaskMap.get(task.getTaskDefinitionKey());

        final Map<String, Object> variables = processEngine.getRuntimeService().getVariables(task.getExecutionId());
        if (userTaskModel.hasMultiInstanceLoopCharacteristics()) {
            log.info("多实例任务转办");

            final String collectionKey = userTaskModel.getLoopCharacteristics().getInputDataItem();
            final String elementVariable = userTaskModel.getLoopCharacteristics().getElementVariable();

            log.info("是否是多实例：{} collectionKey={} elementVariable={}", userTaskModel.hasMultiInstanceLoopCharacteristics(), collectionKey, elementVariable);

            // 顺序集合
            ArrayList<Object> collectionValueList = new ArrayList<>();
            Map<String, Object> map = new HashMap<>();

            final Object collectionValue = task.getProcessVariables().get(collectionKey);

            if (!ObjectUtils.isEmpty(collectionValue) && collectionValue instanceof List) {
                collectionValueList.addAll((List<Object>) collectionValue);

                final int i = collectionValueList.indexOf(oldUserId);
                collectionValueList.addAll(i, userIds);
                collectionValueList.remove(oldUserId);

                // 设置新的流程变量
                processEngine.getTaskService().setVariable(taskId, collectionKey, collectionValueList);
//                collectionValueList.remove(oldUserId);

                // 顺序多实例
                // userTaskModel.getLoopCharacteristics().isSequential()

                for (Object userId : userIds) {
                    map.clear();
                    map.put(elementVariable, userId);
                    // 流程实例id就等于根执行id
                    runtimeService.addMultiInstanceExecution(task.getTaskDefinitionKey(), task.getProcessInstanceId(), map);
                }

                runtimeService.deleteMultiInstanceExecution(task.getExecutionId(), false);
            }

        } else {
            if (task.getAssignee() != null) {
                // 普通任务 会签也有assignee 会签转办设置候选人后，某一个人完成任务后会进入下一会签用户节点
                log.info("普通任务转办");
                processEngine.getTaskService().setAssignee(taskId, null);
                for (Object userId : userIds) {
                    processEngine.getTaskService().addCandidateUser(taskId, userId.toString());
                }
            } else {
                final TaskEntityImpl taskEntity = (TaskEntityImpl) task;
                if (!CollectionUtils.isEmpty(taskEntity.getQueryIdentityLinks())) {
                    log.info("候选人或者候选组转办");
                    // 去除候选人 如果不存在的候选人不会报错
                    processEngine.getTaskService().deleteCandidateUser(taskId, oldUserId);

                    // 去除候选人重复
                    final List<String> old = taskEntity.getQueryIdentityLinks().stream().map(IdentityLinkEntity::getUserId)
                            .filter(Objects::nonNull).collect(Collectors.toList());
                    userIds = userIds.stream().filter(it -> !old.contains(it)).collect(Collectors.toSet());
                    for (Object s : userIds) {
                        processEngine.getTaskService().addCandidateUser(taskId, s.toString());
                    }
                }

            }
        }

        return "ok";
    }

    @ApiOperation("查看任务")
    @GetMapping("/getTask")
    public Object getTask(@RequestParam String taskId) {
        Task task = processEngine.getTaskService().createTaskQuery()
                .taskId(taskId)
                .includeIdentityLinks()
                .includeProcessVariables()
                .singleResult();

        final Map<String, UserTask> allUserTaskMap = getAllUserTaskMap(task.getProcessDefinitionId());
        UserTask userTaskModel = allUserTaskMap.get(task.getTaskDefinitionKey());

        final List<String> activeActivityIds = processEngine.getRuntimeService().getActiveActivityIds(task.getExecutionId());
        final Map<String, Object> variables = processEngine.getRuntimeService().getVariables(task.getExecutionId());
        final Map<String, Object> variablesLocal = processEngine.getRuntimeService().getVariablesLocal(task.getExecutionId());

        final Execution execution = processEngine.getRuntimeService().createExecutionQuery().executionId(task.getExecutionId())
                .singleResult();

        final ProcessInstance processInstance = processEngine.getRuntimeService().createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult();
        final List<ActivityInstance> list = processEngine.getRuntimeService().createActivityInstanceQuery().activityId(task.getTaskDefinitionKey()).list();
        final Map<String, DataObject> dataObjects = processEngine.getTaskService().getDataObjects(taskId);
        final List<Attachment> processInstanceAttachments = processEngine.getTaskService().getProcessInstanceAttachments(task.getProcessInstanceId());
        final ModelQuery modelQuery = processEngine.getRepositoryService().createModelQuery().deploymentId(processInstance.getDeploymentId());
        final ProcessDefinition processDefinition = processEngine.getRepositoryService().getProcessDefinition(task.getProcessDefinitionId());
        final BpmnModel bpmnModel = processEngine.getRepositoryService().getBpmnModel(task.getProcessDefinitionId());

        log.info("task={}", task);

        return task;
    }

    private Map<String, UserTask> getAllUserTaskMap(String processDefinitionId) {
//        ProcessDefinition processDefinition = repositoryService
//                .createProcessDefinitionQuery()
//                .processDefinitionId(processDefinitionId).singleResult();
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
        Process process = bpmnModel.getProcesses().get(0);

        return process.findFlowElementsOfType(UserTask.class)
                .stream().collect(Collectors.toMap(UserTask::getId, a -> a, (k1, k2) -> k1));
    }

    @ApiOperation("跳到结束节点")
    @PostMapping("/toEnd")
    public Object toEnd(@RequestParam String processInstanceId) {

        final List<Task> list = processEngine.getTaskService().createTaskQuery().processInstanceId(processInstanceId).list();
        final List<String> collect = list.stream().map(Task::getTaskDefinitionKey).collect(Collectors.toList());

        //        找到每个元素，包括节点和顺序流
        Process mainProcess = repositoryService.getBpmnModel(list.get(0).getProcessDefinitionId()).getMainProcess();
        Collection<FlowElement> flowElements = mainProcess.getFlowElements();
        for (FlowElement flowElement : flowElements) {
            System.out.println(flowElement.getId());
        }
        String endNodeId = flowElements.stream().filter(f -> f instanceof EndEvent).collect(Collectors.toList()).get(0).getId();

        processEngine.getRuntimeService()
                .createChangeActivityStateBuilder()
                .processInstanceId(processInstanceId)
                .moveActivityIdsToSingleActivityId(collect, endNodeId)
//                .moveActivityIdTo(currentActivityId, endNodeId)
                .changeState();

        return "ok";
    }

    @ApiOperation("流程实例流程图")
    @GetMapping(value = "diagram-view")
    public void diagramView(String processInstanceId, HttpServletResponse httpServletResponse) throws IOException {
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();

        ProcessDefinition pde = repositoryService.getProcessDefinition(processInstance.getProcessDefinitionId());

        if (pde != null && pde.hasGraphicalNotation()) {
            BpmnModel bpmnModel = repositoryService.getBpmnModel(pde.getId());
            ProcessDiagramGenerator diagramGenerator = processEngineConfiguration.getProcessDiagramGenerator();
            InputStream resource = diagramGenerator.generateDiagram(bpmnModel, "png", runtimeService.getActiveActivityIds(processInstance.getId()), Collections.emptyList(),
                    processEngineConfiguration.getActivityFontName(), processEngineConfiguration.getLabelFontName(),
                    processEngineConfiguration.getAnnotationFontName(), processEngineConfiguration.getClassLoader(), 1.0, processEngineConfiguration.isDrawSequenceFlowNameWithNoLabelDI());

//            HttpHeaders responseHeaders = new HttpHeaders();
//            responseHeaders.set("Content-Type", "image/png");
            httpServletResponse.setContentType("image/png");

            IOUtils.copy(resource, httpServletResponse.getOutputStream());
            resource.close();
//            try {
//                return new ResponseEntity<>(IOUtils.toByteArray(resource), responseHeaders, HttpStatus.OK);
//            } catch (Exception e) {
//                throw new FlowableIllegalArgumentException("Error exporting diagram", e);
//            }

        } else {
            throw new FlowableIllegalArgumentException("Process instance with id '" + processInstance.getId() + "' has no graphical notation defined.");
        }
    }

    @ApiOperation("暂停 激活")
    @PostMapping("/suspend")
    public Object suspend(@RequestParam String processInstanceId, @RequestParam Boolean suspend) {

        if (suspend) {
            runtimeService.suspendProcessInstanceById(processInstanceId);
        } else {
            runtimeService.activateProcessInstanceById(processInstanceId);
        }

        return "ok";
    }

    @GetMapping("test1")
    public Object test1(String startUserId) {
        final List<HistoricProcessInstance> page = processEngine.getHistoryService().createHistoricProcessInstanceQuery().startedBy(startUserId).listPage(0, 10);

        return page;
    }

    @GetMapping("test2")
    public Object test2(String processDefinitionName) {
        final List<HistoricProcessInstance> list = processEngine.getHistoryService().createHistoricProcessInstanceQuery().processDefinitionName(processDefinitionName).list();
        final List<HistoricProcessInstance> list2 = processEngine.getHistoryService().createHistoricProcessInstanceQuery().processInstanceNameLike(processDefinitionName).list();
        final List<HistoricProcessInstance> list3 = processEngine.getHistoryService()
                .createNativeHistoricProcessInstanceQuery()
                .sql("")
                .parameter("aa", processDefinitionName)
                .list();

        return list;
    }

    @GetMapping("test3")
    public Object test3(String processDefinitionName) {
        final List<ProcessDefinition> list = processEngine.getRepositoryService().createProcessDefinitionQuery()
                .processDefinitionNameLike("%" + processDefinitionName + "%")
                .list();

        return list;
    }

//    @Primary
//    @RequestMapping(value = "/modeler-app/rest/account", method = RequestMethod.GET, produces = "application/json")
//    public UserRepresentation getAccount() {
////        User user = new UserEntityImpl();
////        user.setId("admin");
////        SecurityUtils.assumeUser(user);
//        UserRepresentation userRepresentation = new UserRepresentation();
//        userRepresentation.setId("admin");
//        userRepresentation.setFirstName("admin");
//        List<String> privileges = new ArrayList<>();
//        privileges.add("flowable-idm");
//        privileges.add("flowable-modeler");
//        privileges.add("flowable-task");
//        userRepresentation.setPrivileges(privileges);
//        return userRepresentation;
//
//    }
}
